#include "DX11HeightMapTerrain.h"
#include "ScreenManager.h"
#include "LightingManager.h"
#include "DX11ShaderManager.h"
#include "DX11ErrorReport.h"
#include "DX11TextureManager.h"
#include "DX11HelpfulFunctions.h"
#include "CameraManager.h"
#include "AntTweakBarManager.h"

DX11HeightMapTerrain::DX11HeightMapTerrain()
{
	m_waterTint = D3DXVECTOR4(5.0f, 26.0f, 52.0f, 1.0f);
}

DX11HeightMapTerrain::~DX11HeightMapTerrain()
{
	for (int i = 0; i != m_chunks.size(); i++)
	{
		m_chunks[i]->ClearVectorData();
		delete m_chunks[i];
	}

	if (m_pWaterRenderTargetTexture) m_pWaterRenderTargetTexture->Release();
	if (m_pWaterRenderTargetView) m_pWaterRenderTargetView->Release();
	if (m_pWaterShaderResourceView) m_pWaterShaderResourceView->Release();

	if (m_pWaterReflectionTargetTexture) m_pWaterReflectionTargetTexture->Release();
	if (m_pWaterReflectionTargetView) m_pWaterReflectionTargetView->Release();
	if (m_pWaterReflectionSRV) m_pWaterReflectionSRV->Release();
}

void DX11HeightMapTerrain::DrawTerrain()
{
	D3DXMatrixIdentity(&m_world);
	for (int chunkNum = 0; chunkNum != m_chunks.size(); chunkNum++)
	{
		for (int i = 0; i != TheLightingManager::Instance()->GetNumberOfLights(); i++)
		{
			Light l = TheLightingManager::Instance()->GetLight(i + 1);
			m_cb.light[i].ambient = l.ambient;
			m_cb.light[i].colour = l.colour;
			m_cb.light[i].direction = l.direction;
			m_cb.light[i].attenuation = l.attenuation;
			m_cb.light[i].range = l.range;
			m_cb.light[i].enabled = l.enabled;
			m_cb.light[i].position = l.position;
			m_cb.light[i].pad = l.pad;
		}


		m_cb.m_outputColour = D3DXVECTOR4(1.0f, 0.0f, 0.0f, 1.0f);

		TheScreenManager::Instance()->SetBuffers(m_chunks[chunkNum]->pVertexBuffer, m_chunks[chunkNum]->pIndexBuffer);
		
		D3DXMatrixTranslation(&m_world, m_width * -0.5f, 0.0f, m_height * -0.5f);

		D3DXMatrixTranspose(&m_cb.m_world, &m_world);
		D3DXMatrixTranspose(&m_cb.m_view, &TheScreenManager::Instance()->GetViewMatrix());
		D3DXMatrixTranspose(&m_cb.m_projection, &TheScreenManager::Instance()->GetProjectionMatrix());
		m_cb.m_outputColour = D3DXVECTOR4(1.0f, 1.0f, 1.0f, 1.0f);

		//m_chunks[chunkNum]->pConstantBuffer = TheScreenManager::Instance()->GetConstantBuffer();

		TheScreenManager::Instance()->GetDeviceContext()->UpdateSubresource(
			m_chunks[chunkNum]->pConstantBuffer, 0, NULL, &m_cb, 0, 0);

		TheScreenManager::Instance()->GetDeviceContext()->VSSetShader(
			TheDX11ShaderManager::Instance()->UseShader("TerrainShader").first, NULL, 0);

		TheScreenManager::Instance()->GetDeviceContext()->VSSetConstantBuffers(0, 1, &m_chunks[chunkNum]->pConstantBuffer);

		TheScreenManager::Instance()->GetDeviceContext()->PSSetConstantBuffers(0, 1, &m_chunks[chunkNum]->pConstantBuffer);

		TheScreenManager::Instance()->GetDeviceContext()->PSSetShaderResources(0, 1, 
			&TheDX11TextureManager::Instance()->UseTexture(L"../../Assets/terrain_texture.jpg"));
		TheScreenManager::Instance()->GetDeviceContext()->PSSetShaderResources(1, 1, 
			&TheDX11TextureManager::Instance()->UseTexture(L"../../Assets/orange_sand.jpg"));
		TheScreenManager::Instance()->GetDeviceContext()->PSSetShaderResources(3, 1, 
			&TheDX11TextureManager::Instance()->UseTexture(L"../../Assets/rock_texture.png"));
		TheScreenManager::Instance()->GetDeviceContext()->PSSetShaderResources(4, 1, 
			&TheDX11TextureManager::Instance()->UseTexture(L"../../Assets/detail_map.jpg"));

		TheScreenManager::Instance()->GetDeviceContext()->PSSetSamplers(0, 1, &m_chunks[chunkNum]->pTextureSamplerState);
		TheScreenManager::Instance()->GetDeviceContext()->PSSetShader(
			TheDX11ShaderManager::Instance()->UseShader("TerrainShader").second, NULL, 0);

		TheScreenManager::Instance()->GetDeviceContext()->DrawIndexed(m_chunks[chunkNum]->numOfIndices, 0, 0);
	}
}

void DX11HeightMapTerrain::Draw()
{
	m_dome.Update();
	//m_waterTint.x /= 256.0f;
	//m_waterTint.y /= 256.0f;
	//m_waterTint.z /= 256.0f;
	m_water.SetColour(D3DXVECTOR4(m_waterTint.x / 256.0f, m_waterTint.y / 256.0f, m_waterTint.z / 256.0f, 1.0f));

	float ClearColour[] = {0.5f, 0.5f, 0.5f, 1.0f};
	// render water reflection
	TheCameraManager::Instance()->GenerateReflectedMatrix();
	TheCameraManager::Instance()->SetReflectedMatrix();

	TheScreenManager::Instance()->GetDeviceContext()->OMSetRenderTargets(1, 
		TheScreenManager::Instance()->GetRenderTargetview(), 
		TheScreenManager::Instance()->GetDepthStencilView());
	DX11::ClearColour(0.5f, 0.5f, 0.5f, 1.0f);
	DX11::CreateRenderTexture(&m_pWaterReflectionTargetView,
								&m_pWaterReflectionSRV,
								ClearColour);

	m_dome.Draw();

	TheCameraManager::Instance()->SetBaseMatrix();
	TheScreenManager::Instance()->GetDeviceContext()->OMSetRenderTargets(1, 
		TheScreenManager::Instance()->GetRenderTargetview(), 
		TheScreenManager::Instance()->GetDepthStencilView());
	DX11::ClearColour(0.5f, 0.5f, 0.5f, 1.0f);
	
	DX11::CreateRenderTexture(&m_pWaterRenderTargetView, &m_pWaterShaderResourceView, ClearColour);
	
	m_dome.Draw();
	DrawTerrain();
	
	TheScreenManager::Instance()->GetDeviceContext()->OMSetRenderTargets(1, 
		TheScreenManager::Instance()->GetRenderTargetview(), 
		TheScreenManager::Instance()->GetDepthStencilView());
	DX11::ClearColour(0.5f, 0.5f, 0.5f, 1.0f);

	
	m_dome.Draw();
	DrawTerrain();

	// draw water and apply refraction texture
	m_water.SetRefractionTexture(m_pWaterShaderResourceView);
	m_water.SetReflectionTexture(m_pWaterReflectionSRV);
	m_water.Draw();

	
}

void DX11HeightMapTerrain::Update()
{
	m_water.Update();
}

bool DX11HeightMapTerrain::Load(const std::string& filename)
{
	return true;
}

bool DX11HeightMapTerrain::GenerateRandom(int width, int height)
{
	float waterHeight = -1000;

	TheDX11TextureManager::Instance()->LoadTexture(L"../../Assets/terrain_texture.jpg");
	TheDX11TextureManager::Instance()->LoadTexture(L"../../Assets/orange_sand.jpg");
	TheDX11TextureManager::Instance()->LoadTexture(L"../../Assets/rock_texture.png");
	TheDX11TextureManager::Instance()->LoadTexture(L"../../Assets/detail_map.jpg");

	// shader resource tests
	//ID3D11ShaderResourceView* pShaderResource;
	//pShaderResource->
	
	FILE *pImgFile;
	BITMAPFILEHEADER bmpFile;
	BITMAPINFOHEADER bmpInfo;

	pImgFile = fopen("../../Assets/hmt7.bmp", "rb");
	if (pImgFile == NULL)
	{
		DX11ErrorMsg(L"Could not find image file", L"Height Map Terrain", MB_OK);
	}

	fread(&bmpFile, sizeof(BITMAPFILEHEADER), 1, pImgFile);
	fread(&bmpInfo, sizeof(BITMAPINFOHEADER), 1, pImgFile);

	m_width = bmpInfo.biWidth;
	m_height = bmpInfo.biHeight;

	//if (m_width > 256 || m_height > 256)
	//{
	//	DX11ErrorMsg(L"Image size is too big", L"Height Map Terrain", MB_OK);
	//}
	if (m_width != m_height)
	{
		DX11ErrorMsg(L"Image height and width must be the same value", L"Height Map Terrain", MB_OK);
	}

	int imageDataSize = m_width * m_height * 4;
	unsigned char* imageData = new unsigned char[imageDataSize];

	fseek(pImgFile, bmpFile.bfOffBits, SEEK_SET);
	fread(imageData, 1, imageDataSize, pImgFile);

	float numOfChunksf = (m_width * m_height) / 65536.0f;
	int numOfChunks = (int)numOfChunksf;

	float yPos = 0;
	int curArrayPos = 0;

	int curXPos = 0, curYPos = 0;
	m_chunks.push_back(new HMTChunk());

	int chunkNum = 0;
	for (int w = 0; w != m_width; w++)
	{
		for (int h = 0; h != m_width; h++)
		{
			yPos = imageData[curArrayPos];
			yPos /= 255.0f;
			yPos *= 50.0f;
			m_chunks[chunkNum]->vertexData.push_back(VertexData());
			m_chunks[chunkNum]->vertexData.back().m_pos = D3DXVECTOR3((float)w, -yPos + 10.0f, (float)h);
			m_chunks[chunkNum]->vertexData.back().m_normal = D3DXVECTOR3(0.0f, -1.0f, 0.0f);
			m_chunks[chunkNum]->vertexData.back().m_texCoord = D3DXVECTOR2((float)w / ((float)m_width - 1.0f), (float)h/ ((float)m_height - 1.0f));

			curArrayPos += 3;
		
			if (waterHeight < m_chunks[chunkNum]->vertexData.back().m_pos.y)
			{
				waterHeight = m_chunks[chunkNum]->vertexData.back().m_pos.y;
			}
		}
	}

	// calculate indices
	for (int h = 0; h != m_height - 1; h++)
	{
		for (int w = 0; w != m_width - 1; w++)
		{
			m_chunks[chunkNum]->indices.push_back((w * m_width) + h);
			m_chunks[chunkNum]->indices.push_back((w * m_width) + h + 1);
			m_chunks[chunkNum]->indices.push_back(((w + 1) * m_width) + h);

			m_chunks[chunkNum]->indices.push_back(((w + 1) * m_width) + h);
			m_chunks[chunkNum]->indices.push_back(w * m_width + h + 1);
			m_chunks[chunkNum]->indices.push_back((w + 1) * m_width + h + 1);
		}
	}

	// calc normals
	for (int i = 0; i != m_chunks[chunkNum]->indices.size() - 2; i++)
	{
		D3DXVECTOR3 vert1 = m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[i]].m_pos;
		D3DXVECTOR3 vert2 = m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[i + 1]].m_pos;
		D3DXVECTOR3 vert3 = m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[i + 2]].m_pos;

		D3DXVECTOR3 vec1 = vert1 - vert3;
		D3DXVECTOR3 vec2 = vert3 - vert2;

		D3DXVECTOR3 normal;
		D3DXVec3Cross(&normal, &vec1, &vec2);
		m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[i]].m_normal.x = normal.x;
		m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[i]].m_normal.y = -normal.y;
		m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[i]].m_normal.z = normal.z;
	}

	// normalize faces
	for (int i = 0; i != m_chunks[chunkNum]->indices.size() - 2; i++)
	{
		int index1 = i;
		int index2 = i + 1;
		int index3 = i + 2;

		D3DXVECTOR3 vert1 = m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[index1]].m_normal;
		D3DXVECTOR3 vert2 = m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[index2]].m_normal;
		D3DXVECTOR3 vert3 = m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[index3]].m_normal;

		D3DXVECTOR3 total = vert1 + vert2 + vert3;

		D3DXVECTOR3 normal;
		D3DXVec3Normalize(&normal, &total);
		m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[i]].m_normal.x = normal.x;
		m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[i]].m_normal.y = normal.y;
		m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[i]].m_normal.z = normal.z;

		m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[i + 1]].m_normal.x = normal.x;
		m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[i + 1]].m_normal.y = normal.y;
		m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[i + 1]].m_normal.z = normal.z;

		m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[i + 2]].m_normal.x = normal.x;
		m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[i + 2]].m_normal.y = normal.y;
		m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[i + 2]].m_normal.z = normal.z;
	}

	// calc tangents
	for (int i = 0; i != m_chunks[chunkNum]->indices.size() - 2; i++)
	{
		VertexData vert1 = m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[i]];
		VertexData vert2 = m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[i + 1]];
		VertexData vert3 = m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[i + 2]];

		D3DXVECTOR3 vec1 = vert1.m_pos - vert3.m_pos;
		D3DXVECTOR3 vec2 = vert3.m_pos - vert2.m_pos;

		D3DXVECTOR2 tangent1, tangent2;
		tangent1.x = vert1.m_texCoord.x - vert3.m_tangent.x;
		tangent1.y = vert1.m_texCoord.y - vert3.m_tangent.y;

		tangent2.x = vert3.m_texCoord.x - vert2.m_tangent.x;
		tangent2.y = vert3.m_texCoord.y - vert2.m_tangent.y;

		D3DXVECTOR3 tangent;
		tangent.x = (tangent1.y * vec1.x * vec2.x) / 
					(1.0f / (tangent1.x * tangent2.y - tangent2.x * tangent1.y));
		tangent.y = (tangent1.y * vec1.y * vec2.y) / 
					(1.0f / (tangent1.x * tangent2.y - tangent2.x * tangent1.y));
		tangent.z = (tangent1.y * vec1.z * vec2.z) / 
					(1.0f / (tangent1.x * tangent2.y - tangent2.x * tangent1.y));

		
		m_chunks[chunkNum]->vertexData[m_chunks[chunkNum]->indices[i]].m_tangent = tangent;
	}
	
	// calc binormals
	for (int i = 0; i != m_chunks[chunkNum]->vertexData.size(); i++)
	{
		D3DXVECTOR3 tangent = m_chunks[chunkNum]->vertexData[i].m_tangent;
		D3DXVECTOR3 normal = m_chunks[chunkNum]->vertexData[i].m_normal;
		D3DXVECTOR3 binormal;
		D3DXVec3Cross(&binormal, &normal, &tangent);

		m_chunks[chunkNum]->vertexData[i].m_binormal.x = binormal.x;
		m_chunks[chunkNum]->vertexData[i].m_binormal.y = binormal.y;
		m_chunks[chunkNum]->vertexData[i].m_binormal.z = binormal.z;
	}
	// create the mesh end 


	m_chunks[chunkNum]->numOfIndices = m_chunks[chunkNum]->indices.size();

	D3D11_SAMPLER_DESC sampDesc;
	ZeroMemory( &sampDesc, sizeof(sampDesc) );
	sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
	sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
	sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
	sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
	sampDesc.MinLOD = 0;
	sampDesc.MaxLOD = D3D11_FLOAT32_MAX;

	HRESULT hr = TheScreenManager::Instance()->GetDevice()->CreateSamplerState(
		&sampDesc, &m_chunks[chunkNum]->pTextureSamplerState);
	TheScreenManager::Instance()->InitBuffers(m_chunks[chunkNum]->pVertexBuffer,
		m_chunks[chunkNum]->pIndexBuffer, m_chunks[chunkNum]->bd, 
		m_chunks[chunkNum]->initData, m_chunks[chunkNum]->vertexData, 
		m_chunks[chunkNum]->indices, &m_chunks[chunkNum]->pConstantBuffer);


	// start water creation
	m_water.Initialize(m_width * 0.5f, waterHeight * 0.8f);
	DX11::InitRenderToTexture(&m_pWaterRenderTargetTexture, 
								&m_pWaterRenderTargetView, 
								&m_pWaterShaderResourceView);

	// reflection texture
	
	DX11::InitRenderToTexture(&m_pWaterReflectionTargetTexture, 
								&m_pWaterReflectionTargetView,
								&m_pWaterReflectionSRV);

	TheAntTweakBarManager::Instance()->AddRWVariable(	"Water Tint (red)", 
														TW_TYPE_FLOAT, 
														&m_waterTint.x,
														"group=Water_Tint min=0 max=255 step=1");
	TheAntTweakBarManager::Instance()->AddRWVariable(	"Water Tint (green)", 
														TW_TYPE_FLOAT, 
														&m_waterTint.y,
														"group=Water_Tint min=0 max=255 step=1");
	TheAntTweakBarManager::Instance()->AddRWVariable(	"Water Tint (blue)", 
														TW_TYPE_FLOAT, 
														&m_waterTint.z,
														"group=Water_Tint min=0 max=255 step=1");
	
	// end water creation

	delete imageData;
	m_dome.Initialise();
	fclose(pImgFile);

	return true;
}